📋 Browser Storage trong JavaScript - Bài 18

🎯 Mục tiêu bài học:

  • Hiểu các loại lưu trữ dữ liệu trong trình duyệt: localStorage, sessionStorage, cookies
  • Biết cách lưu trữ và lấy dữ liệu từ browser storage (bộ nhớ trình duyệt)
  • Nắm vững ưu nhược điểm của từng loại storage (phương thức lưu trữ)
  • Thực hành với các ví dụ thực tế về cookies (bánh quy web)

💡 Browser Storage (Bộ nhớ trình duyệt) là gì?

Browser Storage cho phép các website lưu trữ dữ liệu trực tiếp trên máy tính của người dùng, giúp website hoạt động nhanh hơn và ghi nhớ thông tin người dùng.

🔍 Giải thích từng loại:

📦 localStorage (Bộ nhớ cục bộ):

  • Lưu trữ lâu dài - dữ liệu không bị xóa khi đóng trình duyệt
  • Dung lượng lớn (khoảng 5-10MB)
  • Ví dụ: Lưu cài đặt giao diện, giỏ hàng online

🔄 sessionStorage (Bộ nhớ phiên làm việc):

  • Lưu trữ tạm thời - chỉ tồn tại khi tab/cửa sổ đang mở
  • Tự động xóa khi đóng tab
  • Ví dụ: Form đang điền dở, trạng thái trang hiện tại

🍪 Cookies (Bánh quy web):

  • Lưu trữ nhỏ (tối đa 4KB mỗi cookie)
  • Tự động gửi kèm theo mọi request lên server
  • Có thể set thời gian hết hạn
  • Ví dụ: Đăng nhập tự động, theo dõi người dùng

📚 Từ điển thuật ngữ quan trọng:

🔧 API (Application Programming Interface):
Giao diện lập trình ứng dụng - bộ công cụ để tương tác với browser storage

📊 Capacity (Dung lượng):
Số lượng dữ liệu tối đa có thể lưu trữ

⏰ Persistence (Tính bền vững):
Thời gian dữ liệu tồn tại (lâu dài hay tạm thời)

🌐 HTTP Request:
Yêu cầu gửi từ trình duyệt lên server (máy chủ)

🎯 Session (Phiên làm việc):
Khoảng thời gian từ khi mở website đến khi đóng tab

🔐 Expiration (Thời gian hết hạn):
Thời điểm dữ liệu sẽ bị xóa tự động

⚖️ So sánh chi tiết các loại Storage

📖 Hướng dẫn đọc bảng so sánh:

Bảng dưới đây giúp bạn chọn loại storage phù hợp cho từng tình huống cụ thể.

Loại Storage
(Phương thức lưu trữ)
Dung lượng
(Capacity)
Thời gian tồn tại
(Persistence)
Gửi kèm Request
(Auto-send)
Cách sử dụng
(API Methods)
📦 localStorage
Bộ nhớ cục bộ
~5-10MB
Lớn nhất
Vĩnh viễn
Đến khi người dùng xóa thủ công
❌ Không
Chỉ ở phía client
localStorage.setItem(key, value)
localStorage.getItem(key)
🔄 sessionStorage
Bộ nhớ phiên
~5-10MB
Lớn nhất
Tạm thời
Mất khi đóng tab/cửa sổ
❌ Không
Chỉ ở phía client
sessionStorage.setItem(key, value)
sessionStorage.getItem(key)
🍪 Cookies
Bánh quy web
~4KB
Rất nhỏ, mỗi cookie
Tùy chỉnh
Có thể set thời gian hết hạn
✅ Có
Tự động gửi lên server
document.cookie = "name=value"
Phức tạp hơn, cần helper functions

🎯 Khi nào dùng loại nào?

📦 localStorage - Dùng khi:
  • Lưu cài đặt giao diện (theme, font size)
  • Lưu giỏ hàng online lâu dài
  • Cache dữ liệu để tăng tốc độ
  • Lưu draft bài viết, form chưa submit
🔄 sessionStorage - Dùng khi:
  • Lưu trạng thái trang hiện tại
  • Multi-step form (form nhiều bước)
  • Dữ liệu nhạy cảm không muốn lưu lâu
  • Shopping session tạm thời
🍪 Cookies - Dùng khi:
  • Lưu token đăng nhập (authentication)
  • Theo dõi hành vi người dùng (tracking)
  • Lưu ngôn ngữ, múi giờ
  • Server cần biết thông tin client

⚠️ Những lỗi thường gặp và cách tránh:

🚫 Lỗi thường gặp:
  • QuotaExceededError: Hết dung lượng storage
  • SecurityError: Bị chặn ở chế độ Private/Incognito
  • TypeError: Storage không khả dụng
  • SyntaxError: JSON.parse() lỗi với dữ liệu hỏng
  • Cookie không lưu: Path, domain sai
✅ Cách xử lý:
  • Luôn dùng try-catch để bắt lỗi
  • Kiểm tra storage availability trước khi dùng
  • Có giá trị mặc định (fallback) khi lỗi
  • Validate dữ liệu trước khi JSON.parse()
  • Sử dụng helper functions đã test kỹ

💪 Thực hành Browser Storage

🎯 Hướng dẫn thực hành:

Sử dụng các demo bên dưới để thực hành với localStorage, sessionStorage và Cookies. Mở DevTools để theo dõi changes.

💾 localStorage Demo

localStorage demo output sẽ hiển thị ở đây...

🔄 sessionStorage Demo

sessionStorage demo output sẽ hiển thị ở đây...

🍪 Cookies Demo

Cookie demo output sẽ hiển thị ở đây...

✨ Nguyên tắc và Bảo mật trong Browser Storage

📚 Tại sao cần quan tâm đến bảo mật?

Browser Storage có thể bị khai thác bởi hacker thông qua:

  • XSS (Cross-Site Scripting): Hacker chèn mã độc vào website
  • CSRF (Cross-Site Request Forgery): Tấn công giả mạo request
  • Man-in-the-Middle: Nghe lén dữ liệu truyền tải

🛡️ Nguyên tắc bảo mật cho Cookies:

🔒 Các cờ bảo mật (Security Flags):
  • Secure flag:
    - Chỉ gửi cookie qua HTTPS (mã hóa SSL/TLS)
    - Ngăn cookie bị đánh cắp qua HTTP thường
    - Ví dụ: document.cookie = "token=abc; Secure"
  • HttpOnly flag:
    - Không cho phép JavaScript đọc cookie
    - Ngăn XSS attacks đánh cắp session
    - Chỉ set được từ server, không từ client JS
  • SameSite flag:
    - Strict: Chỉ gửi trong same-site requests
    - Lax: Cho phép GET request từ site khác
    - None: Gửi cho tất cả requests (cần Secure)
⏰ Quản lý thời gian:
  • Expiration hợp lý:
    - Session cookies: Không set expires (tự xóa khi đóng browser)
    - Login tokens: 1-7 ngày
    - User preferences: 30-365 ngày
  • Auto-refresh tokens:
    - Tự động làm mới token trước khi hết hạn
    - Implement sliding session
💾 Quản lý dữ liệu:
  • Tối thiểu hóa: Chỉ lưu dữ liệu thực sự cần thiết
  • Mã hóa: Encrypt sensitive data trước khi lưu
  • Validation: Luôn validate dữ liệu khi đọc ra

❌ TUYỆT ĐỐI KHÔNG được làm:

🚫 Dữ liệu nhạy cảm:
  • Passwords (mật khẩu)
  • Credit card numbers (số thẻ tín dụng)
  • Social Security Numbers (số CMND/CCCD)
  • Private keys (khóa bí mật)
  • API secrets (bí mật API)
⚠️ Lỗi thường gặp:
  • Lưu quá nhiều dữ liệu (vượt limit)
  • Không set thời gian hết hạn
  • Sử dụng cookie cho data không cần server
  • Không validate input trước khi lưu
  • Trust dữ liệu từ storage mà không kiểm tra

✅ Code examples an toàn:

Secure Storage Patterns
javascript
// ✅ An toàn: Luôn sử dụng try-catch
function safeSetStorage(key, value) {
  try {
    // Validate input trước khi lưu
    if (!key || typeof key !== 'string') {
      throw new Error('Key phải là string không rỗng');
    }
    
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch (error) {
    console.error('Lỗi khi lưu storage:', error.message);
    return false;
  }
}

// ✅ An toàn: Có fallback value
function safeGetStorage(key, defaultValue = null) {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) : defaultValue;
  } catch (error) {
    console.warn('Không thể đọc storage, trả về default:', error.message);
    return defaultValue;
  }
}

// ✅ An toàn: Secure cookie với đầy đủ flags
function setSecureCookie(name, value, days = 7) {
  const expires = new Date();
  expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000));
  
  // Chỉ hoạt động trên HTTPS production
  const secureFlag = location.protocol === 'https:' ? '; Secure' : '';
  
  document.cookie = `${name}=${encodeURIComponent(value)}; ` +
    `expires=${expires.toUTCString()}; ` +
    `path=/; ` +
    `SameSite=Strict${secureFlag}`;
}

🐛 Debug và Khắc phục sự cố

🔍 Cách kiểm tra Browser Storage trong DevTools:

🛠️ Chrome DevTools:
  1. Nhấn F12 hoặc chuột phải → Inspect
  2. Vào tab Application (hoặc Storage trong Firefox)
  3. Mở rộng Storage ở sidebar trái
  4. Chọn loại storage muốn xem:
    • Local Storage: Xem localStorage data
    • Session Storage: Xem sessionStorage data
    • Cookies: Xem và edit cookies
⚡ Thao tác nhanh:
  • Xem dữ liệu: Click vào domain trong danh sách
  • Edit giá trị: Double-click vào value cell
  • Xóa item: Chuột phải → Delete hoặc nhấn Delete
  • Xóa tất cả: Chuột phải → Clear
  • Refresh: Icon refresh để cập nhật danh sách

🚨 Các lỗi thường gặp và cách sửa:

1. QuotaExceededError - Hết dung lượng storage

🔍 Nguyên nhân: Lưu quá nhiều dữ liệu, vượt quá limit (~5-10MB)

⚡ Triệu chứng: Console hiển thị "QuotaExceededError"

✅ Cách sửa:

  • Xóa dữ liệu cũ không cần thiết
  • Compress dữ liệu trước khi lưu
  • Sử dụng cleanup function định kỳ
  • Chia nhỏ dữ liệu lớn thành nhiều keys
2. SecurityError - Bị chặn bởi browser

🔍 Nguyên nhân: Private/Incognito mode, hoặc file:// protocol

⚡ Triệu chứng: Storage không hoạt động, SecurityError trong console

✅ Cách sửa:

  • Test trên server thực (http/https), không phải file://
  • Kiểm tra availability trước khi sử dụng
  • Có fallback mechanism khi storage bị disable
3. JSON Parse Error - Dữ liệu bị hỏng

🔍 Nguyên nhân: Dữ liệu không phải JSON hợp lệ

⚡ Triệu chứng: SyntaxError khi JSON.parse()

✅ Cách sửa:

  • Luôn dùng try-catch khi parse JSON
  • Validate dữ liệu trước khi lưu
  • Có default value khi parse fail

🛠️ Template code debug-friendly:

Safe Storage Helper
javascript
// 🛡️ Helper class an toàn với đầy đủ error handling
class SafeStorageHelper {
  
  // Kiểm tra xem storage có khả dụng không
  static isAvailable(storageType = 'localStorage') {
    try {
      const storage = window[storageType];
      const testKey = '__storage_test__';
      storage.setItem(testKey, 'test');
      storage.removeItem(testKey);
      return true;
    } catch (error) {
      console.warn(`${storageType} không khả dụng:`, error.message);
      return false;
    }
  }
  
  // Lưu dữ liệu an toàn với validate
  static setItem(key, value, storageType = 'localStorage') {
    // Kiểm tra availability
    if (!this.isAvailable(storageType)) {
      console.error(`${storageType} không khả dụng`);
      return false;
    }
    
    try {
      // Validate input
      if (!key || typeof key !== 'string') {
        throw new Error('Key phải là string không rỗng');
      }
      
      const storage = window[storageType];
      const serializedValue = JSON.stringify(value);
      
      storage.setItem(key, serializedValue);
      console.log(`✅ Đã lưu ${key} vào ${storageType}`);
      return true;
      
    } catch (error) {
      if (error.name === 'QuotaExceededError') {
        console.error('❌ Hết dung lượng storage! Hãy xóa dữ liệu cũ.');
        this.cleanup(storageType); // Tự động dọn dẹp
      } else {
        console.error('❌ Lỗi khi lưu:', error.message);
      }
      return false;
    }
  }
  
  // Đọc dữ liệu an toàn với fallback
  static getItem(key, defaultValue = null, storageType = 'localStorage') {
    if (!this.isAvailable(storageType)) {
      console.warn(`${storageType} không khả dụng, trả về default value`);
      return defaultValue;
    }
    
    try {
      const storage = window[storageType];
      const item = storage.getItem(key);
      
      if (item === null) {
        console.info(`Key "${key}" không tồn tại trong ${storageType}`);
        return defaultValue;
      }
      
      // Thử parse JSON, fallback về string nếu fail
      try {
        return JSON.parse(item);
      } catch (parseError) {
        console.warn('Không thể parse JSON, trả về raw string:', parseError.message);
        return item; // Trả về string gốc
      }
      
    } catch (error) {
      console.error('❌ Lỗi khi đọc:', error.message);
      return defaultValue;
    }
  }
  
  // Dọn dẹp dữ liệu cũ khi hết dung lượng
  static cleanup(storageType = 'localStorage', keepCount = 10) {
    try {
      const storage = window[storageType];
      const keys = Object.keys(storage);
      
      if (keys.length > keepCount) {
        // Xóa 50% dữ liệu cũ nhất (strategy đơn giản)
        const keysToRemove = keys.slice(0, Math.floor(keys.length / 2));
        keysToRemove.forEach(key => storage.removeItem(key));
        console.log(`🧹 Đã dọn dẹp ${keysToRemove.length} items từ ${storageType}`);
      }
    } catch (error) {
      console.error('❌ Lỗi khi cleanup:', error.message);
    }
  }
  
  // Debug: In ra thông tin storage
  static debugInfo(storageType = 'localStorage') {
    if (!this.isAvailable(storageType)) {
      console.log(`❌ ${storageType} không khả dụng`);
      return;
    }
    
    const storage = window[storageType];
    const itemCount = storage.length;
    let totalSize = 0;
    
    // Tính tổng kích thước
    for (let i = 0; i < itemCount; i++) {
      const key = storage.key(i);
      const value = storage.getItem(key);
      totalSize += key.length + (value ? value.length : 0);
    }
    
    console.log(`📊 ${storageType} Debug Info:`, {
      itemCount,
      totalSize: `${totalSize} characters (~${Math.round(totalSize/1024)}KB)`,
      estimatedCapacity: '5-10MB',
      usage: `${Math.round(totalSize/1024/1024*100)}% (ước tính)`
    });
  }
}

// 💡 Cách sử dụng:
// SafeStorageHelper.setItem('myKey', {data: 'value'});
// const data = SafeStorageHelper.getItem('myKey', {default: 'fallback'});
// SafeStorageHelper.debugInfo(); // Debug thông tin storage

🎯 Checklist debug khi gặp vấn đề:

🔍 Kiểm tra cơ bản:
  • ☐ Có đang ở Private/Incognito mode không?
  • ☐ Website chạy trên server hay file:// ?
  • ☐ Console có báo lỗi gì không?
  • ☐ Storage có dữ liệu trong DevTools không?
  • ☐ Key/value có đúng format không?
🛠️ Các bước debug:
  1. Mở DevTools → Application tab
  2. Xem Storage sections (Local/Session/Cookies)
  3. Check console cho error messages
  4. Test với dữ liệu đơn giản trước
  5. Dùng try-catch để bắt lỗi chi tiết